package com.google.code.sfa.util;

import org.biojava.bio.symbol.AbstractSymbolList;
import org.biojava.bio.symbol.SymbolList;
import java.util.*;
import org.biojava.bio.symbol.Symbol;
import org.biojava.bio.symbol.Alphabet;
import org.biojava.bio.seq.DNATools;
import org.biojava.bio.symbol.Alignment;
import org.biojava.bio.symbol.IllegalSymbolException;
import org.biojava.bio.symbol.AlphabetManager;

public class AggregateSymbolList extends AbstractSymbolList {
    
    private AggregateSymbolNode first, cache;
    private int length, cacheIndex;

    public AggregateSymbolList( SymbolList seed) {
	first = null;
	cacheIndex = 0;
	length = 0;
	for( int i = 1; i <= seed.length(); i++) {
	    insertSymbol( i, seed.symbolAt( i));
	}
    }

    public void set(SymbolList seq, int first) {
	int existingPos = first;
	int seqPos = 1;
	while( existingPos < 1) {
	    insertSymbol( seqPos, seq.symbolAt( seqPos));
	    existingPos++;
	    seqPos++;
	}
	if( first < 1) {
	    existingPos = seqPos;
	}
	while( seqPos <= seq.length() ) {
	    setSymbol( existingPos, seq.symbolAt( seqPos));
	    existingPos++;
	    seqPos++;
	}
    }
    /*
    public void set(SymbolList seq, OverlapAlignment align) {
	Alignment alignment = align.alignment;
	int existingPos, seqPos, alignmentPos;
	
	existingPos = align.offset;
	seqPos = 1;

	while( existingPos < 1) {
	    insertSymbol( seqPos, seq.symbolAt( seqPos));
	    existingPos++;
	    seqPos++;
	}
	if( align.offset < 1) {
	    existingPos = seqPos;
	}
	alignmentPos = 1;
	Symbol gap = DNATools.getDNA().getGapSymbol();
	while( alignmentPos <= alignment.length()) {
	    if( consensusSymbolAt(existingPos) != gap &&
		alignment.symbolAt("subject", alignmentPos) == gap ) {
		insertSymbol( existingPos, alignment.symbolAt("query", alignmentPos));
	    } else {
		setSymbol( existingPos, alignment.symbolAt("query", alignmentPos));
	    }
	    //if( alignment.symbolAt("query", alignmentPos) != gap ) {
	    seqPos++;
	    //}
	    existingPos++;
	    alignmentPos++;
	}

	while( seqPos <= seq.length() ) {
	    setSymbol( existingPos, seq.symbolAt(seqPos));
	    existingPos++;
	    seqPos++;
	}
    }
    */
    public void insertGap( int pos, int gaps) {
	if( gaps < 1) {
	    return;
	}
	Symbol gap = DNATools.getDNA().getGapSymbol();
	insertSymbol( pos, gap);
	gaps--;
	while( gaps > 0) {
	    setSymbol(pos,gap);
	    gaps--;
	}
    }

    public void insertSymbol( int index, Symbol s) {
	if( index == 1) {
	    first = new AggregateSymbolNode(s, first);
	    length++;
	    if( cacheIndex > 0) {
		cacheIndex++;
	    }	    
	    return;
	}
	// insert symbol before index
	advanceCacheTo( index-1);
	AggregateSymbolNode pos = new AggregateSymbolNode( s, cache.getNext());
	int gaps = cache.getSet().totalSize() - 1;
	DNASymbolSet set = pos.getSet();
	while( gaps > 0) {
	    gaps--;
	    set.add( DNATools.getDNA().getGapSymbol()  );
	}
	cache.setNext(pos);
	length++;
    }

    public void setSymbol( int index, Symbol s) {
	if( index == 0 ) {
	    first = new AggregateSymbolNode( s, first);
	    length++;
	    if( cacheIndex > 0) {
		cacheIndex++;
	    }
	} else {
	    DNASymbolSet set;
	    if( index == 1) {
		if( first == null) {
		    first = new AggregateSymbolNode();
		    length++;
		}				   
		set = first.getSet();
	    } else {
		advanceCacheTo( index-1);
		if( cache.getNext() != null) {
		    set = cache.getNext().getSet();
		} else {
		    length++;
		    AggregateSymbolNode last = new AggregateSymbolNode();
		    set = last.getSet();
		    cache.setNext( last);
		}
	    }

	    set.add( s);
	}
    }

    public Alphabet getAlphabet() {
	return DNATools.getDNA();
    }
    public int length() {
	return length;
    }

    private void advanceCacheTo( int index) {
	if( cacheIndex <= 0 || cacheIndex > index) {
	    cacheIndex = 1;
	    cache = first;
	}
	while( cacheIndex < index && cache != null) {
	    cacheIndex++;
	    cache = cache.getNext();
	}
    }

    public Symbol symbolAt(int index) {
	try {
	    advanceCacheTo( index);
	    Set s = cache.getSet();
	    if( s.size() > 1) {
		s = new HashSet( s);
	    }
	    return getAlphabet().getAmbiguity( s);
	} catch( Exception e) {
	    return DNATools.n();
	}
    }

    public Symbol consensusSymbolAt( int index) {
	advanceCacheTo( index);
	DNASymbolSet s = cache.getSet();
	return s.getBestSymbol();
    }

}

class AggregateSymbolNode {
    AggregateSymbolNode next;
    final DNASymbolSet set;

    public AggregateSymbolNode( Symbol s, AggregateSymbolNode next) {
	this.set = new DNASymbolSet();
	this.set.add(s);
	this.next = next;
    }

    public AggregateSymbolNode() {
	this.set = new DNASymbolSet();
	this.next = null;
    }

    public AggregateSymbolNode getNext() {
	return next;
    }

    public void setNext( AggregateSymbolNode next) {
	this.next = next;
    }

    public DNASymbolSet getSet() {
	return set;
    }
}
